#pragma once
#include "RenderingPrefix.h"
#include "Runtime/CGI/CGICommandList.h"
#include "Runtime/CGI/CGIResource.h"

namespace Alice
{
	/** An element type independent interface for uploading an array of resource data. */
	struct FResourceArrayUploadInterface
	{
		virtual ~FResourceArrayUploadInterface() {}

		/** Returns a pointer to the resource data. */
		virtual const void* GetResourceData() const = 0;

		/** Returns size of resource data allocation */
		virtual uint32 GetResourceDataSize() const = 0;

		/** Called on non-UMA systems after the RHI has copied the resource data, and no longer needs the CPU's copy. */
		virtual void Discard() = 0;
	};

	/** Utility to do a simple upload of data from an array managed by the caller. */
	struct FResourceArrayUploadArrayView : public FResourceArrayUploadInterface
	{
		const void* const Data;
		const uint32 SizeInBytes;

		FResourceArrayUploadArrayView() = delete;

		FResourceArrayUploadArrayView(const void* InData, uint32 InSizeInBytes)
			: Data(InData)
			, SizeInBytes(InSizeInBytes)
		{
		}

		/*template<typename ElementType>
		FResourceArrayUploadArrayView(TConstArrayView<ElementType> View)
			: Data(View.GetData())
			, SizeInBytes(View.Num() * View.GetTypeSize())
		{
		}

		template<typename ElementType, typename AllocatorType>
		FResourceArrayUploadArrayView(const TArray<ElementType, AllocatorType>& InArray)
			: FResourceArrayUploadArrayView(MakeArrayView(InArray))
		{
		}*/

		// FResourceArrayUploadInterface
		virtual const void* GetResourceData() const final
		{
			return Data;
		}

		virtual uint32 GetResourceDataSize() const final
		{
			return SizeInBytes;
		}

		virtual void Discard() final
		{
			// do nothing
		}
	};

	/**
	 * An element type independent interface to the resource array.
	 */
	class FResourceArrayInterface : public FResourceArrayUploadInterface
	{
	public:

		/**
		 * @return true if the resource array is static and shouldn't be modified
		 */
		virtual bool IsStatic() const = 0;

		/**
		 * @return true if the resource keeps a copy of its resource data after the RHI resource has been created
		 */
		virtual bool GetAllowCPUAccess() const = 0;

		/** 
		 * Sets whether the resource array will be accessed by CPU. 
		 */
		virtual void SetAllowCPUAccess( bool bInNeedsCPUAccess ) = 0;
	};


	/**
	 * Allows for direct GPU mem allocation for bulk resource types.
	 */
	class FResourceBulkDataInterface
	{
	public:

		virtual ~FResourceBulkDataInterface() {}

		/** 
		 * @return ptr to the resource memory which has been preallocated
		 */
		virtual const void* GetResourceBulkData() const = 0;

		/** 
		 * @return size of resource memory
		 */
		virtual uint32 GetResourceBulkDataSize() const = 0;

		/**
		 * Free memory after it has been used to initialize RHI resource 
		 */
		virtual void Discard() = 0;
		
		enum class EBulkDataType
		{
			Default,
			MediaTexture,
			VREyeBuffer,
		};
		
		/**
		 * @return the type of bulk data for special handling
		 */
		virtual EBulkDataType GetResourceType() const
		{
			return EBulkDataType::Default;
		}
	};


	/**
	 * Allows for direct GPU mem allocation for texture resource.
	 */
	class FTexture2DResourceMem : public FResourceBulkDataInterface
	{
	public:

		/**
		 * @param MipIdx index for mip to retrieve
		 * @return ptr to the offset in bulk memory for the given mip
		 */
		virtual void* GetMipData(int32 MipIdx) = 0;

		/**
		 * @return total number of mips stored in this resource
		 */
		virtual int32	GetNumMips() = 0;

		/** 
		 * @return width of texture stored in this resource
		 */
		virtual int32 GetSizeX() = 0;

		/** 
		 * @return height of texture stored in this resource
		 */
		virtual int32 GetSizeY() = 0;

		/**
		 * @return Whether the resource memory is properly allocated or not.
		 */
		virtual bool IsValid() = 0;

		/**
		 * @return Whether the resource memory has an async allocation request and it's been completed.
		 */
		virtual bool HasAsyncAllocationCompleted() const = 0;

		/** Blocks the calling thread until the allocation has been completed. */
		virtual void FinishAsyncAllocation() = 0;

		/** Cancels any async allocation. */
		virtual void CancelAsyncAllocation() = 0;

		virtual ~FTexture2DResourceMem() {}
	};
    class RenderResource
    {
    public:
        virtual void InitResource(CGICommandListBase& inCGICmdList);
        /**
         * Initializes the RHI resources used by this resource.
         * Called when entering the state where both the resource and the RHI have been initialized.
         * This is only called by the rendering thread.
         */
        virtual void InitRHI(CGICommandListBase& inCGICmdList) {}
        static CGIBuffer* CreateRHIBufferInternal(
             CGICommandListBase& RHICmdList,
             const TCHAR* InDebugName,
             const TCHAR* InOwnerName,
             uint32 ResourceCount,
             EBufferUsageFlags InBufferUsageFlags,
             FResourceArrayInterface* ResourceArray,
             bool bWithoutNativeResource
        );
        template<typename T>
        CGIBuffer* CreateRHIBuffer(CGICommandListBase& RHICmdList, T& InOutResourceObject, uint32 ResourceCount, EBufferUsageFlags InBufferUsageFlags, const TCHAR* InDebugName)
        {
            CGIBuffer* buffer;

            FResourceArrayInterface* ResourceArray = (FResourceArrayInterface*)InOutResourceObject;//InOutResourceObject ? InOutResourceObject->GetResourceArray() : nullptr;
            if (ResourceCount != 0)
            {
				buffer = CreateRHIBufferInternal(RHICmdList, InDebugName, TEXT("Default"), ResourceCount, InBufferUsageFlags, ResourceArray, InOutResourceObject == nullptr);
            }

            // If the buffer creation emptied the resource array, delete the containing structure as well
            /*if (ShouldFreeResourceObject(InOutResourceObject, ResourceArray))
            {
             delete InOutResourceObject;
             InOutResourceObject = nullptr;
            }*/

            return buffer;
        }
        static CGICommandListBase& GetImmediateCommandList();
    };
	class FShaderCodeReader
	{
	public:
	};
	class FShaderCode
	{
	public:
	};
	class FShaderCodeResource
	{
	public:
	};
	class FShaderMapResource : public RenderResource
	{
	public:
		CGIShader* mCGIShaders[SF_NumGraphicsFrequencies];
		inline CGIShader* GetShader(int32 ShaderIndex, bool bRequired = true)
		{
			// This is a double checked locking. This trickery arises from the fact that we're
			// synchronizing two threads: one that takes a lock and another that doesn't.
			// Without fences, there is a race between storing the shader pointer and accessing it
			// on the other (lockless) thread.
			CGIShader* Shader = mCGIShaders[ShaderIndex];//.load(std::memory_order_acquire);
			/*if (UNLIKELY(Shader == nullptr))
			{
				Shader = CreateShaderOrCrash(ShaderIndex, bRequired);
			}*/
			return Shader;
		}
		inline bool HasShader(int32 ShaderIndex) const
		{
			return mCGIShaders[ShaderIndex]!=nullptr;//.load(std::memory_order_acquire) != nullptr;
		}
	};
	class FShaderMapResourceCode
	{
	public:
	};
    /** A vertex buffer resource */
    class VertexBuffer : public RenderResource
    {
    public:
        CGIBuffer* mCGIBuffer;
    };
    class RenderCommandPipe
    {
    public:
    };
    /**
     * Sends a message to the rendering thread to initialize a resource.
     * This is called in the game thread.
     */
    void BeginInitResource(RenderResource* inResource, RenderCommandPipe* inRenderCommandPipe = nullptr);

    /**
     * Sends a message to the rendering thread to update a resource.
     * This is called in the game thread.
     */
    void BeginUpdateResourceRHI(RenderResource* inResource, RenderCommandPipe* inRenderCommandPipe = nullptr);

    /**
     * Sends a message to the rendering thread to release a resource.
     * This is called in the game thread.
     */
    void BeginReleaseResource(RenderResource* inResource, RenderCommandPipe* inRenderCommandPipe = nullptr);

    /**
    * Enables the batching of calls to BeginReleaseResource
    * This is called in the game thread.
    */
    void StartBatchedRelease();

    /**
    * Disables the batching of calls to BeginReleaseResource
    * This is called in the game thread.
    */
    void EndBatchedRelease();

    /**
     * Sends a message to the rendering thread to release a resource, and spins until the rendering thread has processed the message.
     * This is called in the game thread.
     */
    void ReleaseResourceAndFlush(RenderResource* inResource);
}